import { Constructor } from "../../ecs/ECSWorld";
import { InjectableObject } from "../../inject";
import { ICommandMap } from "../base/ICommandMap";
import { Command } from "../Command";

type Type_Command_Executor = (handler: CommandEventHandler, ...args: any[]) => void;

class CommandEventHandler {
    _commandExecutor: Type_Command_Executor = null;
    _isOneshot = false;

    constructor(commandExecutor: Type_Command_Executor, oneshot: boolean) {
        this._commandExecutor = commandExecutor;
        this._isOneshot = oneshot;
    }

    public set isOneshot(oneshot: boolean) {
        this._isOneshot = oneshot;
    }

    public get isOneshot(): boolean {
        return this._isOneshot;
    }

    onCommandEvent(...args: any[]) {
        if (!this._commandExecutor) {
            return;
        }
        this._commandExecutor(this, ...args);
    }
}

export class CommandMap extends InjectableObject implements ICommandMap {

    eventTypeMap: Map<string, Map<string, CommandEventHandler>> = new Map;
    detainedCommands: Map<Object, number> = new Map;

    detain(command: Command) {
        let refCnt = this.detainedCommands.get(command);
        if (!refCnt) {
            refCnt = 0;
        }

        refCnt++;

        this.detainedCommands.set(command, refCnt);
    }

    release(command: Command) {
        let refCnt = this.detainedCommands.get(command);
        if (refCnt) {
            refCnt--;
            if (refCnt === 0) {
                command.dispose();
                this.detainedCommands.delete(command);
            }
        }
    }

    execute(commandClazz: string | Constructor<Command>, ...args: any[]) {
        let cmdInst = this.injector.instantiateOf(commandClazz);

        if (typeof commandClazz === 'string') {

        }

        this.detain(cmdInst);
        cmdInst.execute(...args);
        this.release(cmdInst);
    }

    mapCommand(event: string, commandClazz?: string | Constructor<Command>, oneshot?: boolean) {

        oneshot = oneshot === undefined ? false : true;
        commandClazz = commandClazz === undefined ? event : commandClazz;

        let eventHandlers = this.eventTypeMap.get(event);
        if (!eventHandlers) {
            eventHandlers = new Map;
            this.eventTypeMap.set(event, eventHandlers);
        }

        let clazzName
        if (typeof commandClazz == 'string') {
            clazzName = commandClazz;
        } else {
            clazzName = commandClazz.name;
        }

        let handler = eventHandlers.get(clazzName);
        if (handler) {
            handler.isOneshot = oneshot;
        } else {

            let this__ = this;
            handler = new CommandEventHandler((h: CommandEventHandler, ...args: any[]) => {
                if (h.isOneshot) {
                    this__.unmapCommand(event, commandClazz);
                }

                this__.execute(commandClazz, ...args);
            }, oneshot);
        }

        eventHandlers.set(clazzName, handler);

        this.dispatcher.on(event, handler.onCommandEvent, handler);
    }


    unmapCommand(event: string, commandClazz: string | Constructor<Command>) {
        let eventHandlers = this.eventTypeMap.get(event);
        if (!eventHandlers) {
            return;
        }

        let clazzName
        if (typeof commandClazz == 'string') {
            clazzName = commandClazz;
        } else {
            clazzName = commandClazz.name;
        }

        let handler = eventHandlers.get(clazzName);
        if (handler) {
            this.dispatcher.off(event, handler.onCommandEvent, handler);
            eventHandlers.delete(clazzName);
        }

    }

    unmapCommands() {
        this.eventTypeMap.forEach((value: Map<string, CommandEventHandler>, key: string) => {
            value.forEach((handler, event: string) => {
                this.dispatcher.off(event, handler.onCommandEvent, handler);
            });

            value.clear();
        });

        this.eventTypeMap.clear();
    }

    hasEventCommand(event: string, commandClazz: string | Constructor<Command>): boolean {
        let eventHandlers = this.eventTypeMap.get(event);
        if (!eventHandlers) {
            return false;
        }

        let clazzName
        if (typeof commandClazz == 'string') {
            clazzName = commandClazz;
        } else {
            clazzName = commandClazz.name;
        }

        let handler = eventHandlers.get(clazzName);
        if (handler) {
            return true;
        }

        return false;
    }

}